﻿/*
** Xin YUAN, 2023, BSD (2)
*/

////////////////////////////////////////////////////////////////////////////////

// PIPE_HANDLE_STATE
/*! \brief A structure for pipe handle state.

A structure for pipe handle state.
*/
typedef struct _tagPipeHandleState
{
	DWORD dwState;                //!< The current state of the handle.
	DWORD dwCurInstances;         //!< The number of current pipe instances.
	DWORD dwMaxCollectionCount;   //!< The maximum number of bytes to be collected on the client's computer before transmission to the server.
	DWORD dwCollectDataTimeout;   //!< The maximum time, in milliseconds, that can pass before a remote named pipe transfers information over the network.
	WCHAR szUserName[_MAX_PATH];  //!< The user name string associated with the client application.
} PIPE_HANDLE_STATE, *LPPIPE_HANDLE_STATE;

// PIPE_INFO
/*! \brief A structure for pipe information.

A structure for pipe information.
*/
typedef struct _tagPipeInfo
{
	DWORD dwFlags;          //!< The type of the named pipe.
	DWORD dwOutBufferSize;  //!< The size of the buffer for outgoing data, in bytes.
	DWORD dwInBufferSize;   //!< The size of the buffer for incoming data, in bytes.
	DWORD dwMaxInstances;   //!< The maximum number of pipe instances that can be created.
} PIPE_INFO, *LPPIPE_INFO;

//_w_async_pipe

/*! \brief A class for asynchronous pipe.

A class for asynchronous pipe.
*/
class _w_async_pipe
{
public:
	/*! \brief Constructor.

	Constructor.
	*/
	explicit _w_async_pipe(HANDLE hPipe = INVALID_HANDLE_VALUE) noexcept : m_hPipe(hPipe)
	{
	}
	_w_async_pipe(const _w_async_pipe&) = delete;
	_w_async_pipe(_w_async_pipe&& src) noexcept : m_hPipe(src.m_hPipe)
	{
		src.m_hPipe = INVALID_HANDLE_VALUE;
	}
	/*! \brief Destructor.

	Destructor.
	*/
	~_w_async_pipe() noexcept
	{
		Close();
	}

	_w_async_pipe& operator=(const _w_async_pipe&) = delete;
	_w_async_pipe& operator=(_w_async_pipe&& src) noexcept
	{
		if( this != &src ) {
			if( m_hPipe == src.m_hPipe ) {
				assert( m_hPipe == INVALID_HANDLE_VALUE );
			}
			else {
				Close();
				m_hPipe = src.m_hPipe;
				src.m_hPipe = INVALID_HANDLE_VALUE;
			}
		}
		return *this;
	}

	/*! \brief Default cast operator.

	Default cast operator.
	*/
	operator HANDLE() const noexcept
	{
		return m_hPipe;
	}
	/*! \brief Check if the handle is valid.

	Check if the handle is valid.
	\return true for valid, false for invalid.
	*/
	bool IsValid() const noexcept
	{
		return m_hPipe != INVALID_HANDLE_VALUE;
	}

	/*! \brief Close the handle.

	Close the handle.
	*/
	void Close() noexcept
	{
		if( m_hPipe != INVALID_HANDLE_VALUE ) {
			BOOL bRet = ::CloseHandle(m_hPipe);
			(void)bRet;
			assert( bRet );
			m_hPipe = INVALID_HANDLE_VALUE;
		}
	}

	/*! \brief Attach a pipe handle.

	Attach a pipe handle.
	\param hPipe [in] A pipe handle.
	*/
	void Attach(HANDLE hPipe) noexcept
	{
		if( m_hPipe != hPipe )
			Close();
		m_hPipe = hPipe;
	}
	/*! \brief Detach a pipe handle.

	Detach a pipe handle.
	\return A pipe handle.
	*/
	HANDLE Detach() noexcept
	{
		HANDLE hPipe = m_hPipe;
		m_hPipe = INVALID_HANDLE_VALUE;
		return hPipe;
	}

	/*!\ brief Creates an instance of a named pipe.

	Creates an instance of a named pipe in server end.
	\param lpszName [in] The unique pipe name with the form as L"\\\\.\\pipe\\pipename".
	\param dwOpenMode [in] The open mode.
	\param dwPipeMode [in] The pipe mode.
	\param nMaxInstance [in] The maximum number of instances that can be created for this pipe (1-255).
	\param nOutBufferSize [in] The number of bytes to reserve for the output buffer.
	\param nInBufferSize [in] The number of bytes to reserve for the input buffer.
	\param nDefaultTimeOut [in] The default time-out value in milliseconds.
	\param lpSecurityAttributes [in] A pointer to a SECURITY_ATTRIBUTES structure that
	                                 specifies a security descriptor for the new named pipe and
	                                 determines whether child processes can inherit the returned handle.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL Create(LPCWSTR lpszName, DWORD dwOpenMode = PIPE_ACCESS_DUPLEX,
				DWORD dwPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
				DWORD nMaxInstance = PIPE_UNLIMITED_INSTANCES,
				DWORD nOutBufferSize = 4096, DWORD nInBufferSize = 4096,
				DWORD nDefaultTimeOut = 0,
				LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL) noexcept
	{
		assert( lpszName != NULL );
		assert( !IsValid() );
		m_hPipe = ::CreateNamedPipeW(lpszName,
						dwOpenMode | FILE_FLAG_OVERLAPPED,
						dwPipeMode | PIPE_WAIT,
						nMaxInstance, nOutBufferSize, nInBufferSize,
						nDefaultTimeOut, lpSecurityAttributes);
		return m_hPipe != INVALID_HANDLE_VALUE;  //FALSE, ::GetLastError()
	}
	/*! \brief Accept the client process pipe connection.

	Enables a named pipe server process to wait for a client process to connect to
	an instance of a named pipe.
	\param lpOverlapped [in] A pointer to an OVERLAPPED structure. The OVERLAPPED structure should
	                         contain a handle to a manual-reset event object (which the server can
	                         create by using the CreateEvent function).
	\return TRUE for succeeded, FALSE for failed.
	        ERROR_IO_PENDING means the operation is pending.
	        User can use the WaitForSingleObject to determine when the state of the event object
	        is signaled and then use the GetOverlappedResult function to retrieve the results
	        after the operation has finished.
	        ERROR_PIPE_CONNECTED means there is a good connection between client and server.
	        This handle can be used for reading/writing directly.
	        ERROR_NO_DATA means the handle should be disconnected or closed.
	        This can be used to check if the previous client has closed its handle.
	        (only in nonblocking mode).
	        ERROR_PIPE_LISTENING means no client is connected. User should use
	        this method again for acceptance (only in nonblocking mode).
	        TRUE means the first time it is called for a pipe instance that is disconnected from
	        a previous client. This indicates that the pipe is now available to be connected to
	        a new client process.
	        Other values mean failure.
	\note This is an asynchronous operation.
	*/
	BOOL Accept(LPOVERLAPPED lpOverlapped) noexcept
	{
		assert( IsValid() && lpOverlapped != NULL );
		return ::ConnectNamedPipe(m_hPipe, lpOverlapped);  //FALSE, ::GetLastError()
	}
	/*! \brief Disconnects the server end of a named pipe instance.

	Disconnects the server end of a named pipe instance from a client process.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL Disconnect() noexcept
	{
		assert( IsValid() );
		return ::DisconnectNamedPipe(m_hPipe);  //FALSE, ::GetLastError()
	}
	/*! \brief Open a pipe in client end.

	Open a pipe in client end.
	\param lpszName [in] The name of pipe.
	\param dwAccess [in] The requested access to the file or device, which can be summarized as read,
	                     write, both or neither (zero).
	\return TRUE for succeeded, FALSE for failed.
	        ERROR_PIPE_BUSY user should call ::WaitNamedPipeW to wait for a minute,
	        and call CreateFileW again.
	*/
	BOOL Open(LPCWSTR lpszName, DWORD dwAccess = GENERIC_READ | GENERIC_WRITE) noexcept
	{
		assert( !IsValid() );
		m_hPipe = ::CreateFileW(lpszName, dwAccess, 0, NULL, OPEN_EXISTING,
						FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
		return m_hPipe != INVALID_HANDLE_VALUE;  //FALSE, ::GetLastError()
	}
	/*! \brief Retrieves information about a specified named pipe.

	Retrieves information about a specified named pipe.
	\param bServer [in] This pipe handle is to the server end.
	\param bSameMachine [in] Client and server processes are on the same computer.
	\param lpState [out] Receive the information.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL GetState(BOOL bServer, BOOL bSameMachine, PIPE_HANDLE_STATE* lpState) const noexcept
	{
		assert( IsValid() );
		assert( lpState != NULL );

		//clear
		::ZeroMemory(lpState, sizeof(PIPE_HANDLE_STATE));

		return ::GetNamedPipeHandleStateW(m_hPipe, &(lpState->dwState), &(lpState->dwCurInstances),
						(bServer || bSameMachine) ? NULL : &(lpState->dwMaxCollectionCount),
						(bServer || bSameMachine) ? NULL : &(lpState->dwCollectDataTimeout),
						(!bServer) ? NULL : lpState->szUserName,
						_MAX_PATH);  //FALSE, ::GetLastError()
	}
	/*! \brief Sets the read mode and the blocking mode of the specified named pipe.

	Sets the read mode and the blocking mode of the specified named pipe.
	\param bServer [in] This pipe handle is to the server end.
	\param bSameMachine [in] Client and server processes are on the same computer.
	\param lpState [in] Specify the state. dwState(dwMode), dwMaxCollectionCount, dwCollectDataTimeout
	                    are valid members to be used to set pipe handle state.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL SetState(BOOL bServer, BOOL bSameMachine, const PIPE_HANDLE_STATE* lpState) noexcept
	{
		assert( IsValid() );
		assert( lpState != NULL );
		return ::SetNamedPipeHandleState(m_hPipe, (LPDWORD)&(lpState->dwState),
						(bServer || bSameMachine) ? NULL : (LPDWORD)&(lpState->dwMaxCollectionCount),
						(bServer || bSameMachine) ? NULL : (LPDWORD)&(lpState->dwCollectDataTimeout)
						);  //FALSE, ::GetLastError()
	}
	/*! \brief Retrieves information about the specified named pipe.

	Retrieves information about the specified named pipe.
	\param lpInfo [out] Receive the information of this pipe.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL GetInfo(PIPE_INFO* lpInfo) const noexcept
	{
		assert( IsValid() );
		assert( lpInfo != NULL );

		::ZeroMemory(lpInfo, sizeof(PIPE_INFO));

		return ::GetNamedPipeInfo(m_hPipe, &(lpInfo->dwFlags), &(lpInfo->dwOutBufferSize),
						&(lpInfo->dwInBufferSize), &(lpInfo->dwMaxInstances)
						);  //FALSE, ::GetLastError()
	}
	/*! \brief Transact message in pipe.

	Combines the functions that write a message to and read a message from the specified named pipe
	into a single network operation.
	\param lpInBuffer [in] A pointer to the buffer containing the data to be written to the pipe.
	\param nInBufferSize [in] The size of the input buffer, in bytes.
	\param lpOutBuffer [out] A pointer to the buffer that receives the data read from the pipe.
	\param nOutBufferSize [in] The size of the output buffer, in bytes.
	\param lpBytesRead [out] A pointer to the variable that receives the number of bytes read from
	                         the pipe. If lpOverlapped is NULL, lpBytesRead cannot be NULL.
	                         If lpOverlapped is not NULL, lpBytesRead can be NULL.
	\param lpOverlapped [in, out] A pointer to an OVERLAPPED structure.
	\return TRUE for succeeded, FALSE for failed.
	        ERROR_IO_PENDING means the operation is pending.
	        ERROR_MORE_DATA means the remainder of the message can be read
	        by a subsequent call to ReadFile, ReadFileEx, or PeekNamedPipe.
	*/
	BOOL Transact(LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
					LPDWORD lpBytesRead, LPOVERLAPPED lpOverlapped) noexcept
	{
		assert( IsValid() );
		return ::TransactNamedPipe(m_hPipe, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize,
						lpBytesRead, lpOverlapped);  //FALSE, ::GetLastError()
	}
	/*! \brief Copies data from a pipe into a buffer without removing it from the pipe.

	Copies data from a named or anonymous pipe into a buffer without removing it from the pipe.
	\param lpBuffer [out] A pointer to a buffer that receives data read from the pipe.
	                      This parameter can be NULL if no data is to be read.
	\param nBufferSize [in] The size of the buffer specified by the lpBuffer parameter, in bytes.
	\param lpBytesRead [out] A pointer to a variable that receives the number of bytes read from the pipe.
	                         This parameter can be NULL if no data is to be read.
	\param lpTotalBytesAvail [out] A pointer to a variable that receives the total number of bytes
	                               available to be read from the pipe. This parameter can be NULL
	                               if no data is to be read.
	\param lpBytesLeftThisMessage [out] A pointer to a variable that receives the number of bytes
	                                    remaining in this message. This parameter will be zero for
	                                    byte-type named pipes or for anonymous pipes.
	                                    This parameter can be NULL if no data is to be read.
	\return TRUE for succeeded, FALSE for failed.
	*/
	BOOL Peek(LPVOID lpBuffer = NULL, DWORD nBufferSize = 0, LPDWORD lpBytesRead = NULL,
				LPDWORD lpTotalBytesAvail = NULL, LPDWORD lpBytesLeftThisMessage = NULL) noexcept
	{
		assert( IsValid() );
		return ::PeekNamedPipe(m_hPipe, lpBuffer, nBufferSize, lpBytesRead,
						lpTotalBytesAvail, lpBytesLeftThisMessage);  //FALSE, ::GetLastError()
	}
	/*! \brief Begin a read operation.

	Begin a read operation.
	\param lpBuf [out] A pointer to the buffer that receives the data read from pipe.
	\param dwCount [in] The maximum number of bytes to be read.
	\param lpOverlapped [in, out] A pointer to an OVERLAPPED structure is required.
	\return TRUE for succeeded, FALSE for failed.
	        ERROR_IO_PENDING means the operation is pending.
	        ERROR_MORE_DATA means the remainder of the message can be read
	        by a subsequent call to ReadFile, ReadFileEx, or PeekNamedPipe.
	*/
	BOOL Read(void* lpBuf, DWORD dwCount, LPOVERLAPPED lpOverlapped) noexcept
	{
		assert( IsValid() );
		assert( lpBuf != NULL && dwCount > 0 && lpOverlapped != NULL );
		return ::ReadFile(m_hPipe, lpBuf, dwCount, NULL, lpOverlapped);  //FALSE, ::GetLastError()
	}
	/*! \brief Begin a write operation.

	Begin a write operation.
	\param lpBuf [in] A pointer to the buffer containing the data to be written to pipe.
	\param dwCount [in] The number of bytes to be written to pipe.
	\param lpOverlapped [in, out] A pointer to an OVERLAPPED structure is required.
	\return TRUE for succeeded, FALSE for failed.
	        ERROR_IO_PENDING means the operation is pending.
	\note Check the actual written bytes on completion.
	*/
	BOOL Write(const void* lpBuf, DWORD dwCount, LPOVERLAPPED lpOverlapped) noexcept
	{
		assert( IsValid() );
		assert( lpBuf != NULL && dwCount > 0 && lpOverlapped != NULL );
		return ::WriteFile(m_hPipe, lpBuf, dwCount, NULL, lpOverlapped);  //FALSE, ::GetLastError()
	}
	/*! \brief Get overlapped result.

	Get overlapped result.
	\param lpOverlapped [in] A pointer to an OVERLAPPED structure that was specified when
	                         the overlapped operation was started.
	\param lpcbTransfer [out] A pointer to a variable that receives the number of bytes
	                          that were actually transferred by a read or write operation.
	\param fWait [in] If this parameter is TRUE, the function does not return until the operation
	                  has been completed. If this parameter is FALSE and the operation is still pending,
	                  the function returns FALSE with error value ERROR_IO_INCOMPLETE.
	\return TRUE for succeeded, FALSE for failed.
	        ERROR_IO_INCOMPLETE means the operation is still pending.
	*/
	BOOL GetOverlappedResult(LPOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait) noexcept
	{
		assert( IsValid() );
		assert( lpOverlapped != NULL && lpcbTransfer != NULL );
		return ::GetOverlappedResult(m_hPipe, lpOverlapped, lpcbTransfer, fWait);  //FALSE, ::GetLastError()
	}

	/*! \brief Flushes the buffers of a specified file.

	Flushes the buffers of a specified file and causes all buffered data to be written to a file.
	\return TRUE for succeeded, FALSE for failed.
	\note This is a synchronous operation.
	*/
	BOOL Flush() noexcept
	{
		assert( IsValid() );
		return ::FlushFileBuffers(m_hPipe);  //FALSE, ::GetLastError()
	}

protected:
	HANDLE m_hPipe;  //!< Handle to the named pipe.
};

// PipeHelper
/*! \brief A helper class for named pipe.

A helper class for named pipe.
*/
class PipeHelper
{
public:
	/*! \brief Connects to a message-type pipe.

	Connects to a message-type pipe (and waits if an instance of the pipe is not available),
	writes to and reads from the pipe, and then closes the pipe.
	\param lpNamedPipeName [in] The pipe name.
	\param lpInBuffer [in] The data to be written to the pipe.
	\param nInBufferSize [in] The size of the write buffer, in bytes.
	\param lpOutBuffer [out] A pointer to the buffer that receives the data read from the pipe.
	\param nOutBufferSize [in] The size of the read buffer, in bytes.
	\param lpBytesRead [out] A pointer to a variable that receives the number of bytes read from the pipe.
	\param nTimeOut [in] The number of milliseconds to wait for the named pipe to be available.
	\return TRUE for succeeded, FALSE for failed.
	        ERROR_MORE_DATA means the message size is longer than nOutBufferSize.
	\note This method = CreateFile / WaitNamedPipe + TransactNamedPipe, + CloseHandle.
	*/
	static BOOL CallMessagePipe(LPCWSTR lpNamedPipeName, LPVOID lpInBuffer, DWORD nInBufferSize,
					LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesRead,
					DWORD nTimeOut = NMPWAIT_USE_DEFAULT_WAIT) noexcept
	{
		return ::CallNamedPipeW(lpNamedPipeName, lpInBuffer, nInBufferSize,
						lpOutBuffer, nOutBufferSize, lpBytesRead, nTimeOut);  //FALSE, ::GetLastError()
	}
	/*! \brief Waits the specified named pipe is available for connection.

	Waits until either a time-out interval elapses or an instance of the specified named pipe
	is available for connection in client end.
	\param lpNamedPipeName [in] The name of the named pipe.
	\param nTimeOut [in] The number of milliseconds that the function will wait for an instance
	                     of the named pipe to be available.
	\return TRUE for succeeded, FALSE for failed.
	        ERROR_SEM_TIMEOUT means the function fails with time-out interval expiration.
	*/
	static BOOL WaitPipe(LPCWSTR lpNamedPipeName, DWORD nTimeOut = NMPWAIT_USE_DEFAULT_WAIT) noexcept
	{
		return ::WaitNamedPipeW(lpNamedPipeName, nTimeOut);  //FALSE, ::GetLastError()
	}
};

////////////////////////////////////////////////////////////////////////////////
